package org.sample.project.custom;

import liquibase.resource.AbstractResourceAccessor;
import liquibase.resource.PathResource;
import liquibase.resource.Resource;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.sample.project.core.DbFor;
import org.sample.project.exception.NoChangesetException;
import org.sample.project.util.DatabaseTypeUtil;
import org.sample.project.util.VersionComparator;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.util.*;

/**
 * 自定义资源访问器
 * Created by foolrabbit on 2025-01-22.
 */
public class MyResourceAccessor extends AbstractResourceAccessor {

    private final String COMMON = "common";

    private String currentVersion;
    private DbFor dbFor;
    private String folder;

    private String dbType;

    public MyResourceAccessor(String folder, String currentVersion, DbFor dbFor, Connection connection) {
        this.folder = folder;
        this.currentVersion = currentVersion;
        this.dbFor = dbFor;
        try {
            this.dbType = DatabaseTypeUtil.getDatabaseTypeByConnection(connection);
        }
        catch (Exception e) {
            // 无法识别时仅执行通用脚本
            this.dbType = "";
        }
    }

    @Override
    public List<Resource> search(String path, boolean recursive) throws IOException {
        Path versionPath = Paths.get(folder, path);
        List<String> needUpgradeVersions = new ArrayList<>();
        // 获取路径下所有的版本文件夹
        List<File> versionFolders = Arrays.asList(versionPath.toFile().listFiles());
        for(File versionFolder : versionFolders){
            // 排查文件，如changelog.xml
            if(!versionFolder.isDirectory()){
                continue;
            }
            String version = versionFolder.getName();
            if(VersionComparator.compareVersions(version, currentVersion) > 0){
                needUpgradeVersions.add(version);
            }
        }
        if(needUpgradeVersions.size() > 0){
            Collections.sort(needUpgradeVersions, new VersionComparator());
        }
        else{
            return Collections.emptyList();
        }

        List<Resource> resources = new ArrayList<>();
        for(String version : needUpgradeVersions){
            resources.addAll(getVersionResources(version));
        }

        if(resources.size() == 0){
            throw new NoChangesetException();
        }

        return resources;
    }

    @Override
    public List<Resource> getAll(String path) throws IOException {
        Resource resource = new PathResource(path, Paths.get(folder, path));
        return Arrays.asList(resource);
    }

    @Override
    public List<String> describeLocations() {
        return Collections.singletonList("my-resource-accessor");
    }

    @Override
    public void close() throws Exception {

    }

    /**
     * 获取指定版本文件夹的资源
     * 将common和指定dbtype下的资源进行合并后执行
     * @param version
     * @return
     * @throws IOException
     */
    private List<Resource> getVersionResources(String version) throws IOException{
        Map<String, String> modules = new CaseInsensitiveMap<>();
        List<String> dbTypes = new ArrayList<>();

        Path versionPath = Paths.get(folder, version);
        List<File> dbTypeFolders = Arrays.asList(versionPath.toFile().listFiles());
        // 获取common和数据库类型的资源
        for(File dbTypeFolder : dbTypeFolders){
            String dbType = dbTypeFolder.getName();
            if(dbType.equalsIgnoreCase(COMMON) || dbType.equalsIgnoreCase(this.dbType)){
                dbTypes.add(dbType);
                List<File> moduleFolds = Arrays.asList(dbTypeFolder.listFiles());
                for(File moduleFolder : moduleFolds){
                    String module = moduleFolder.getName();
                    if(!module.matches(dbFor.getModuleReg()) || modules.containsKey(module)){
                        continue;
                    }
                    modules.put(module, null);
                }
            }
        }

        List<String> moduleNames = new ArrayList<>(modules.keySet());
        // 排序
        Collections.sort(moduleNames);
        List<Resource> resources = new ArrayList<>();
        for(String module : moduleNames){
            resources.addAll(getModuleResources(version, dbTypes, module));
        }

        return resources;
    }

    private List<Resource> getModuleResources(String version, List<String> dbTypes, String module) throws IOException{
        // key=scriptFolder,value=scriptFilename
        MultiValuedMap<String, String> moduleScriptName = new ArrayListValuedHashMap();
        // 不同dbtype文件夹下可能存在同名文件，未减少一次遍历，先记录下来；key=scriptFolder,scriptFilename
        MultiValuedMap<String, String> moduleScriptPath = new ArrayListValuedHashMap();

        for(String dbType : dbTypes){
            Path modulePath = Paths.get(folder, version, dbType, module);
            if(!modulePath.toFile().exists()){
                continue;
            }
            // 获取module下所有文件夹的文件
            File[] scriptFolders = modulePath.toFile().listFiles();
            for(File scriptFolder : scriptFolders){
                File[] scriptFiles = scriptFolder.listFiles();
                for(File scriptFile : scriptFiles){
                    String fileName = scriptFile.getName();
                    if(!fileName.matches(dbFor.getFileReg())){
                        continue;
                    }
                    moduleScriptName.put(scriptFolder.getName(), scriptFile.getName());
                    String key = scriptFolder.getName() + "," + scriptFile.getName();
                    moduleScriptPath.put(key, scriptFile.getAbsolutePath());
                }
            }
        }

        List<Resource> resources = new ArrayList<>();
        // 按module名称排序
        List<String> moduleNames = new ArrayList<>(moduleScriptName.keySet());
        Collections.sort(moduleNames);
        for(String moduleName : moduleNames){
            // 按script名称排序
            List<String> scriptNames = new ArrayList<>(moduleScriptName.get(moduleName));
            Collections.sort(scriptNames);
            for(String scriptName : scriptNames){
                String key = moduleName + "," + scriptName;
                List<String> paths = new ArrayList<>(moduleScriptPath.get(key));
                for(String path : paths){
                    String logicalPath = path.substring(folder.length());
                    Resource resource = new PathResource(logicalPath, Paths.get(path));
                    resources.add(resource);
                }
            }
        }

        return resources;
    }
}
